# ๐ 04. ์คํธ๋ฆผ(Stream) ํ์ฉ | slice | map | match | find | reduce | ...
๐คจ ๋ณธ๊ฒฉ์ ์ผ๋ก ์คํธ๋ฆผ์ ํ์ฉํด๋ณด์! ๋จผ์ , DISH
ํด๋์ค์ MainMethod
๋ฅผ ์์ฑํด์ฃผ์.
# ๐ฝ๏ธ DISH ํด๋์ค
package javaStudy;
public class Dish {
private final String name;
private final boolean vegetarian;
private final int calories;
private final Type type;
public Dish(String name, boolean vegetarian, int calories, Type type) {
this.name = name;
this.vegetarian = vegetarian;
this.calories = calories;
this.type = type;
}
public String getName() {
return name;
}
public boolean isVegetarian() {
return vegetarian;
}
public int getCalories() {
return calories;
}
public Type getType() {
return type;
}
public enum Type {
MEAT,
FISH,
OTHER
}
@Override
public String toString() {
return "Dish{" +
"name='" + name + '\'' +
", vegetarian=" + vegetarian +
", calories=" + calories +
", type=" + type +
'}';
}
}
package javaStudy.mda05;
import javaStudy.Dish;
import java.util.Arrays;
import java.util.List;
public class MainMethod {
public static List<Dish> menu = Arrays.asList(
new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french fries", true, 530, Dish.Type.OTHER),
new Dish("rice", true, 350, Dish.Type.OTHER),
new Dish("season fruit", true, 120, Dish.Type.OTHER),
new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("prawns", false, 300, Dish.Type.FISH),
new Dish("salmon", false, 450, Dish.Type.FISH)
);
public static void main(String[] args) {
}
}
# ๐ ํํฐ๋ง
์คํธ๋ฆผ ์ธํฐํ์ด์ค๋ filter
๋ฉ์๋๋ฅผ ์ง์ํ๋ค. Predicate
๋ฅผ ์ธ์๋ก ๋ฐ์์ ์ผ์นํ๋ ๋ชจ๋ ์์๋ฅผ ํฌํจํ๋ ์คํธ๋ฆผ์ ๋ฐํํ๋ค.
// [Dish{name='french fries', vegetarian=true, calories=530, type=OTHER}, Dish{name='rice', vegetarian=true, calories=350, type=OTHER}, Dish{name='season fruit', vegetarian=true, calories=120, type=OTHER}, Dish{name='pizza', vegetarian=true, calories=550, type=OTHER}]
List<Dish> vegetarianMenu = menu.stream().filter(Dish::isVegetarian).collect(toList());
# ๊ณ ์ ์์๋ก ํํฐ๋ง
๊ณ ์ ์์๋ก ์ด๋ฃจ์ด์ง ์คํธ๋ฆผ์ ๋ฐํํ๋ distinct
๋ฉ์๋๋ ์ง์ํ๋ค.
//2
//4
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);
# ๐ ์คํธ๋ฆผ ์ฌ๋ผ์ด์ฑ
Java 9
์ ์คํธ๋ฆผ์ ์์๋ฅผ ํจ๊ณผ์ ์ผ๋ก ์ ํํ ์ ์๋๋ก takeWhile
dropWhile
๋๊ฐ์ง ์๋ก์ด ๋ฉ์๋๋ฅผ ์ง์ํ๋ค.
# takeWhile
์ด๋ฏธ ์ ๋ ฌ๋ List
๋ฅผ ํํฐ๋งํ์์ ๊ฒฝ์ฐ, ๋ฐ๋ณต ์์
์ด ์๊ธธ ์ ์๋ค. ์ด๋ ์ฌ์ฉํ๋ ๊ฒ takeWhile
์ด๋ค. ์ฐธ์ด ๋๋ ์์๊น์ง๋ง ํฌํจํ๋๋ก ํ๋ค.
์๋์ ๊ฐ์ ๊ฒฝ์ฐ, ์นผ๋ก๋ฆฌ ์์ผ๋ก ์ ๋ ฌ๋์ด ์๋ค๋ ์ฌ์ค์ ํ์ธํ์ฌ 400์นผ๋ก๋ฆฌ ์ดํ์ผ๋๊น์ง๋ง ์คํธ๋ฆผ์ ํฌํจํ๋๋ก ํ์๋ค.
List<Dish> specialMenu = Arrays.asList(
new Dish("season fruit", true, 120, Dish.Type.OTHER),
new Dish("rice", true, 350, Dish.Type.OTHER),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("salmon", false, 450, Dish.Type.FISH),
new Dish("french fries", true, 530, Dish.Type.OTHER),
new Dish("beef", false, 700, Dish.Type.MEAT));
//[Dish{name='season fruit', vegetarian=true, calories=120, type=OTHER}, Dish{name='rice', vegetarian=true, calories=350, type=OTHER}]
List<Dish> slicedMenu1
= specialMenu.stream()
.takeWhile(dish -> dish.getCalories() < 400)
.collect(toList());
# dropWhile
dropWhile
์ takeWhile
๊ณผ ์ ๋ฐ๋์ ์์
์ ์ํํ๋ค. ๊ฑฐ์ง์ด ๋๋ ์ง์ ๊น์ง ๋ฐ๊ฒฌ๋ ์์๋ฅผ ๋ฒ๋ฆฐ๋ค.
// [Dish{name='chicken', vegetarian=false, calories=400, type=MEAT}, Dish{name='salmon', vegetarian=false, calories=450, type=FISH}, Dish{name='french fries', vegetarian=true, calories=530, type=OTHER}, Dish{name='beef', vegetarian=false, calories=700, type=MEAT}]
List<Dish> slicedMenu2
= specialMenu.stream()
.dropWhile(dish -> dish.getCalories() < 400)
.collect(toList());
# ์คํธ๋ฆผ ์ถ์
// [Dish{name='chicken', vegetarian=false, calories=400, type=MEAT}, Dish{name='salmon', vegetarian=false, calories=450, type=FISH}, Dish{name='french fries', vegetarian=true, calories=530, type=OTHER}]
List<Dish> limitDishes
= specialMenu.stream()
.filter(dish -> dish.getCalories() > 350)
.limit(3) // 3๊ฐ ๋ฐํ
.collect(toList());
# ์์ ๊ฑด๋๋ฐ๊ธฐ
// [Dish{name='french fries', vegetarian=true, calories=530, type=OTHER}, Dish{name='beef', vegetarian=false, calories=700, type=MEAT}]
List<Dish> skipDishes
= specialMenu.stream()
.filter(dish -> dish.getCalories() > 350)
.skip(2) // ์ฒ์ 2๊ฐ ์์ ์ ์ธํ๊ณ ๋ฐํ
.collect(toList());
# ๐ ๋งคํ (map)
ํน์ ๊ฐ์ฒด์์ ํน์ ๋ฐ์ดํฐ๋ฅผ ์ ํํ๋ ์์ ! ์์ฃผ ์ํ๋๋ ์ฐ์ฐ์ด๋ฏ๋ก ์ ์ฉํ ๊ฑฐ ๊ฐ๋ค.
# ์คํธ๋ฆผ์ ๊ฐ ์์์ ํจ์ ์ ์ฉ
๊ฐ ์์์ ์ ์ฉํ ๊ฒฐ๊ณผ๊ฐ ์๋ก์ด ์์๋ก ๋งคํ๋๋ค.
// [pork, beef, chicken, french fries, rice, season fruit, pizza, prawns, salmon]
List<String> dishNames
= menu.stream()
.map(Dish::getName)
.collect(toList());
// [4, 4, 7, 12, 4, 12, 5, 6, 6]
List<Integer> dishNameLengths
= menu.stream()
.map(Dish::getName)
.map(String::length)
.collect(toList());
# ์คํธ๋ฆผ ํ๋ฉดํ
List
์์ ๊ณ ์ ๋ฌธ์๋ก ์ด๋ฃจ์ด์ง List
๋ฅผ ๋ฐํํด๋ณด์.
์๋์ ๊ฐ์ด ์๊ฐํ ์ ์์ ๊ฒ์ด๋ค.
List<String> words = Arrays.asList("Hello", "world");
List<String[]> distinct
= words.stream()
.map(s -> s.split(""))
.distinct()
.collect(toList());
ํ์ง๋ง ์ ๋ฐฉ๋ฒ์ Stream<String[]>
์ ๋ฐํํ ๋ฟ๋๋ฌ Hello
์ World
๋ก String[]์ด ๋ด๊ฒจ ๊ณ ์ ๋ฌธ์๋ฅผ ๋ฐํํ์ง๋ ์๋๋ค... ์ฐ๋ฆฌ๊ฐ ์ํ๋ ๊ฒ์ Stream<String>
์ด๋คใ
ใ
# map๊ณผ Arrays.stream (opens new window) ํ์ฉ
๋ฌธ์์ด์ ๋ฐ์ Stream
์ ๋ง๋ค์ด์ฃผ๋ Arrays.stream()
๋ฉ์๋๊ฐ ์๋ค.
String[] arrayOfWords = {"Goodbye", "World"};
Stream<String> streamOfwords = Arrays.stream(arrayOfWords);
List<String> words = Arrays.asList("Hello", "world");
List<Stream<String>> distinct
= words.stream()
.map(s -> s.split(""))
.map(Arrays::stream)
.distinct()
.collect(toList());
ํ์ง๋ง ์ ๋ฉ์๋๋ List<Stream<String>>
์ด ๋ง๋ค์ด์ ธ์ ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋์ง ์์๋ค.
# flapMap
flatMap
์ ์ฌ์ฉํ๋ฉด ๋ค์์ฒ๋ผ ๋ฌธ์ ํด๊ฒฐ ๊ฐ๋ฅ!
// [H, e, l, o, w, r, d]
List<String> distinct =
words.stream()
.map(s -> s.split(""))
.flatMap(Arrays::stream)// ์์ฑ๋ ์คํธ๋ฆผ์ ํ๋์ ์คํธ๋ฆผ์ผ๋ก ํ๋ฉดํ
.distinct()
.collect(toList());
# ๐ ๊ฒ์๊ณผ ๋งค์นญ
ํน์ ์์ฑ์ด ๋ฐ์ดํฐ ์งํฉ์ ์๋์ง ์ฌ๋ถ๋ฅผ ๊ฒ์ํ๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ
# anyMatch()
Predicate
๊ฐ ์ฃผ์ด์ง ์คํธ๋ฆผ์์ ์ ์ด๋ ํ ์์์ ์ผ์นํ๋์ง ํ์ธํ ๋
if (menu.stream().anyMatch(Dish::isVegetarian)) {
System.out.println("The menu is vegetarian friendly");
}
# allMatch()
๋ชจ๋ ์์๊ฐ ์ฃผ์ด์ง Predicate
์ ์ผ์นํ๋์ง ํ์ธํ ๋
// true
boolean isHealthy = menu.stream()
.allMatch(dish -> dish.getCalories() < 1000);
# noneMatch()
allMatch
์ ๋ฐ๋๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค. ์ ๋ฉ์๋๋ฅผ ์๋์ ๊ฐ์ด ๋ค์ ๊ตฌํํ ์ ์๋ค.
// true
boolean isHealthy = menu.stream()
.noneMatch(dish -> dish.getCalories() >= 1000);
# findAny()
ํ์ฌ ์คํธ๋ฆผ์์ ์ผ์นํ๋ ์์์ ์์๋ฅผ ๋ฐํํ๋ค.
Optional<Dish> dish = menu.stream()
.filter(Dish::isVegetarian)
.findAny();
# findFirst()
์ฒซ๋ฒ์งธ ์์๋ฅผ ๋ฐํํ๋ค.
List<Integer> someNumbers = Arrays.asList(1,2,3,4,5);
Optional<Integer> firstSquareDivisibleByThree =
someNumbers.stream()
.map(n -> n * n)
.filter(n -> n % 3 == 0)
.findFirst();
# ๐ ๋ฆฌ๋์ฑ (Reduce)
"๋ฉ๋ด์ ๋ชจ๋ ์นผ๋ก๋ฆฌ์ ํฉ๊ณ๋ฅผ ๊ตฌํ์์ค" ๊ฐ์ด ์คํธ๋ฆผ ์์๋ฅผ ์กฐํฉํด์ ๋ ๋ณต์กํ ์ง์๋ฅผ ํํํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ณด์.
# ์์์ ํฉ/๊ณฑ
reduce
๋ 2๊ฐ์ ์ธ์๋ฅผ ๊ฐ๋๋ค.
๐ ์ด๊น๊ฐ 0
๐ ๋ ์์๋ฅผ ์กฐํฉํด์ ์๋ก์ด ๊ฐ์ ๋ง๋๋ BinaryOperator<T>
์์ ์์๋ ๋๋คํํ์ ์ฌ์ฉ
List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5);
int sum = numberList.stream().reduce(0, (a, b) -> a + b); // 15
int product = numberList.stream().reduce(1, (a, b) -> a * b); // 120
# ์ต๋๊ฐ/์ต์๊ฐ
์ด๊น๊ฐ์ ๋ฐ์ง ์๋๋ก ์ค๋ฒ๋ก๋๋ reduce
๋ ์๋ค. ์ด reduce
๋ Optional
๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ค.
Optional<Integer> max = numberList.stream().reduce(Integer::max); // Optional[5]
Optional<Integer> min= numberList.stream().reduce(Integer::min); // Optional[1]
# ๐ ์ซ์ํ ์คํธ๋ฆผ
์คํธ๋ฆผ ์์์ ํฉ์ ๊ตฌํ๋ ์์ ๊ฐ ์๋์ ๊ฐ๋ค.
int calories = menu.stream().map(Dish::getCalories).reduce(0, Integer::sum);
์ฌ์ค ์ ์ฝ๋์๋ ๋ฐ์ฑ ๋น์ฉ์ด ์จ๊ฒจ์ ธ ์๋ค. ๋ด๋ถ์ ์ผ๋ก ํฉ๊ณ๋ฅผ ๊ณ์ฐํ๊ธฐ ์ ์ Integer๋ฅผ ๊ธฐ๋ณธํ์ผ๋ก ์ธ๋ฐ์ฑํด์ผํ๋ค. ์ง์ .sum()
๋ฉ์๋๋ฅผ ํธ์ถํ ์ ์๋ค๋ฉด ๋ ์ข์ง ์์๊น?
๋คํํ๋ ์ซ์ ์คํธ๋ฆผ์ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์๋๋ก ๊ธฐ๋ณธํ ํนํ ์คํธ๋ฆผ์ ์ ๊ณตํ๋ค.
# ๊ธฐ๋ณธํ ํนํ ์คํธ๋ฆผ
Java 8
์์๋ IntStream
DoubleStream
LongStream
์ธ๊ฐ์ง ๊ธฐ๋ณธํ ํนํ ์คํธ๋ฆผ์ ์ ๊ณตํ๋ค.
๐ ์ซ์ ์คํธ๋ฆผ์ผ๋ก ๋งคํ
int calories = menu.stream()
.mapToInt(Dish::getCalories) //IntStream
.sum();
๐ ๊ฐ์ฒด ์คํธ๋ฆผ์ผ๋ก ๋ณต์ํ๊ธฐ
- ์ซ์ ์คํธ๋ฆผ์ ๋ง๋ ๋ค์์ ์์ํ์ธ ํนํ๋์ง ์์ ์คํธ๋ฆผ์ผ๋ก ๋ณต์ํ ์ ์๋ค.
IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();
๐ ๊ธฐ๋ณธ๊ฐ: OptionalInt
- ํฉ๊ณ ์์ ์์๋ 0์ด๋ผ๋ ๊ธฐ๋ณธ๊ฐ์ด ์์ผ๋ฏ๋ก ๋ณ ๋ฌธ์ ๊ฐ ์์ง๋ง, ์ต๋๊ฐ ์์ ์์๋ ์๋ชป๋ ๊ฒฐ๊ณผ๊ฐ ๋์ถ๋ ์๋ ์๋ค. ์ด๋, ๊ฐ์ด ์กด์ฌํ๋์ง ์ฌ๋ถ๋ฅผ ๊ฐ๋ฆฌํฌ ์ ์๋ ์ปจํ
์ด๋ ํด๋์ค
Optional
์ ํ์ฉํ๋ค.OptionalInt
OptionalDouble
OptionalLong
์ธ๊ฐ์ง ๊ธฐ๋ณธํ ํนํ ์คํธ๋ฆผ ๋ฒ์ ๋ ์ ๊ณตํ๋ค.
OptionalInt maxCalories = menu.stream().mapToInt(Dish::getCalories).max();
int max = maxCalories.orElse(1); // ๊ฐ์ด ์์ ๋ ๊ธฐ๋ณธ ์ต๋๊ฐ์ ๋ช
์์ ์ผ๋ก ์ค์
๐ ์ซ์ ๋ฒ์
- ํน์ ๋ฒ์์ ์ซ์๋ ์ด์ฉํ ์ ์๋ค.
IntStream
๊ณผLongStream
์์๋range
์rangeClosed
๋ผ๋ ๋๊ฐ์ง ์ ์ ๋ฉ์๋๋ฅผ ์ ๊ณตํ๋ค. ์ฒซ๋ฒ์งธ ์ธ์๋ก ์์๊ฐ์, ๋๋ฒ์งธ ์ธ์๋ก ์ข ๋ฃ๊ฐ์ ๊ฐ๋๋ค.
IntStream evenNumbers = IntStream.rangeClosed(1, 100).filter(n -> n % 2 == 0);
# ๐ ์คํธ๋ฆผ ๋ง๋ค๊ธฐ
๋ค์ํ ๋ฐฉ์์ผ๋ก ์คํธ๋ฆผ์ ๋ง๋๋ ๋ฐฉ๋ฒ์ ์ค๋ช ํ์.
# ๊ฐ์ผ๋ก ์คํธ๋ฆผ ๋ง๋ค๊ธฐ (Stream.of)
//MODERN
//JAVA
//IN
//ACTION
Stream<String> stream = Stream.of("Modern","Java","In","Action");
stream.map(String::toUpperCase).forEach(System.out::println);
Stream<String> emptyStream = Stream.empty(); // ์คํธ๋ฆผ์ ๋น์ธ ์ ์๋ค.
# null์ด ๋ ์ ์๋ ๊ฐ์ฒด๋ก ์คํธ๋ฆผ ๋ง๋ค๊ธฐ (Stream.ofNullable)
๊ธฐ์กด์๋ ๋ค์์ฒ๋ผ null์ ๋ช ์์ ์ผ๋ก ํ์ฉํด์ผ ํ๋ค.
String homeValue = System.getProperty("home");
Stream<String> homeValueStream
= homeValue == null ? Stream.empty() : Stream.of(homeValue);
Stream.ofNullable
์ ์ด์ฉํด ๋ค์์ฒ๋ผ ์ฝ๋๋ฅผ ๊ตฌํํ ์ ์๋ค.
Stream<String> homeValueStream
= Stream.ofNullable(System.getProperty("home"));
# ๋ฐฐ์ด๋ก ์คํธ๋ฆผ ๋ง๋ค๊ธฐ (Arrays.stream)
๊ธฐ๋ณธํ int
๋ก ์ด๋ฃจ์ด์ง ๋ฐฐ์ด์ IntStream
์ผ๋ก ๋ณํํ ์ ์๋ค.
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
# ํ์ผ๋ก ์คํธ๋ฆผ ๋ง๋ค๊ธฐ
long uniqueWords = 0;
try (Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) {
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
.distinct().count();
} catch (IOException e) {
e.printStackTrace();
}
# ํจ์๋ก ๋ฌดํ ์คํธ๋ฆผ ๋ง๋ค๊ธฐ
๋ฌดํ ์คํธ๋ฆผ์ ๋ง๋ค ์ ์๋ ๋ ์ ์ ๋ฉ์๋ Stream.iterate
Stream.generate
๋ฅผ ์ ๊ณตํ๋ค.
๐ iterate
- ์ฐ์๋ ์ผ๋ จ์ ๊ฐ์ ๋ง๋ค ๋๋
iterate
๋ฅผ ์ฌ์ฉํ๋ค.
// 0
// 2
// 4
// 6
// 8
Stream.iterate(0, n -> n + 2).limit(5).forEach(System.out::println);
๐ generate
iterate
๊ณผ ๋ฌ๋ฆฌ ์ฐ์์ ์ผ๋ก ๊ณ์ฐํ์ง ์๊ณ ,Supplier<T>
๋ฅผ ์ธ์๋ก ๋ฐ์ ์๋ก์ด ๊ฐ์ ์์ฑํ๋ค.
// 0.19486211698371403
// 0.31687022682444055
// 0.5129741768745474
Stream.generate(Math::random).limit(3).forEach(System.out::println);
# Reference
๋ชจ๋ ์๋ฐ ์ธ ์ก์ (ํ๋น๋ฏธ๋์ด)